package com.zeng.user.mybatis.annotation;

import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author zyb
 * @date 2021-05-09 21:57
 */
public class MybatisBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes attr = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableMybatis.class.getName()));
        if (attr == null) {
            return;
        }

        BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactoryBean.class);
        String configLocation = attr.getString("configLocation");
        if (StringUtils.hasText(configLocation)) {
            bdb.addPropertyValue("configLocation", configLocation);
        }
        String configurationRef = attr.getString("configurationRef");
        if (StringUtils.hasText(configurationRef)) {
            bdb.addPropertyReference("configuration", configurationRef);
        }
        String[] mapperLocations = attr.getStringArray("mapperLocations");
        boolean hasMapperLocations = mapperLocations.length > 0 && !(mapperLocations.length == 1 && "".equals(mapperLocations[0]));
        if (hasMapperLocations) {
            bdb.addPropertyValue("mapperLocations", mapperLocations);
        }
        String dataSourceRef = attr.getString("dataSourceRef");
        if (StringUtils.hasText(dataSourceRef)) {
            bdb.addPropertyReference("dataSource", dataSourceRef);
        }
        String environment = attr.getString("environment");
        if (StringUtils.hasText(environment)) {
            bdb.addPropertyValue("environment", environment);
        }
        bdb.addPropertyValue("failFast", attr.getBoolean("failFast"));
        String typeHandlersPackage = attr.getString("typeHandlersPackage");
        if (StringUtils.hasText(typeHandlersPackage)) {
            bdb.addPropertyValue("typeHandlersPackage", typeHandlersPackage);
        }
        bdb.addPropertyValue("vfs", attr.getClass("vfs"));

        String[] pluginsRefs = attr.getStringArray("pluginsRef");
        List<String> pluginRefList = Arrays.stream(pluginsRefs)
                .filter(StringUtils::hasText)
                .collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(pluginRefList)) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(InterceptorFactoryBean.class);
            ManagedList<RuntimeBeanReference> references = new ManagedList<>();
            pluginRefList.stream().map(RuntimeBeanReference::new).forEach(references::add);
            beanDefinitionBuilder.addPropertyValue("sources", references);
            registry.registerBeanDefinition("pluginsRefs", beanDefinitionBuilder.getBeanDefinition());
            bdb.addPropertyReference("plugins", "pluginsRefs");
        }

        Class<?>[] typeHandlers = attr.getClassArray("typeHandlers");
        if (typeHandlers.length > 0) {
            TypeHandler<?>[] typeHandlerObjs = new TypeHandler[typeHandlers.length];
            for (int i = 0, typeHandlersLength = typeHandlers.length; i < typeHandlersLength; i++) {
                Class<?> typeHandler = typeHandlers[i];
                typeHandlerObjs[i] = (TypeHandler<?>) unwrap(typeHandler);
            }
            bdb.addPropertyValue("typeHandlers", typeHandlerObjs);
        }
        registry.registerBeanDefinition(attr.getString("value"), bdb.getBeanDefinition());

    }

    private <T> T unwrap(Class<T> clazz) {
        T value = null;
        try {
            value = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return value;
    }
}
